package com.example.base.task

import kotlinx.coroutines.*
import java.util.concurrent.CancellationException
import kotlin.jvm.Throws

open class ProgressTask<PROGRESS, RESULT> : ITask {

    private var isSuccess: Boolean = false
    private var isCancelled: Boolean = false
    private var running = false
    private var hasError = false

    private var progressAction: ((PROGRESS) -> Unit)? = null
    private var successAction: ((RESULT) -> Unit)? = null
    private var errorAction: ((Throwable) -> Unit)? = null
    private var beforeAction: (() -> Unit)? = null
    private var interruptedAction: (() -> Unit)? = null

    val isRunning: Boolean
        get() = job != null && !job!!.isCancelled && running && !isCancelled

    private val mainDispatcher = if (Task.ANDROID_PLATFORM) Dispatchers.Main else Dispatchers.Default

    private var job: Job? = null

    /**
     * 启动任务
     */
    fun start(): ProgressTask<PROGRESS, RESULT> {
        job = GlobalScope.launch(start = CoroutineStart.LAZY, context = Dispatchers.Default) {
            runCatching {
                if (isRunning)
                    throw IllegalStateException("already started")

                isSuccess = false
                isCancelled = false
                hasError = false
                running = true

                // BEFORE
                launch(mainDispatcher) {
                    onTaskBefore()
                    beforeAction?.invoke()
                }
                if (isCancelled)
                    throw RuntimeException("task is already cancelled")
                return@runCatching onTaskRun()
            }.onSuccess { result ->
                launch(mainDispatcher) {
                    running = false
                    isSuccess = true
                    onTaskSuccess(result)
                    successAction?.invoke(result)
                }
            }.onFailure {
                launch(mainDispatcher) {
                    if (!isCancelled) {
                        running = false
                        isSuccess = false
                        hasError = true
                        onTaskError(it)
                        errorAction?.invoke(it)
                    } else {
                        if (!isSuccess && !hasError) {
                            if (isCancelled && it is CancellationException) {
                                onTaskInterrupted()
                                interruptedAction?.invoke()
                            }
                        }
                    }
                }
            }
        }
        job?.start()
        return this
    }

    /**
     * 主动中断任务
     */
    open fun cancel() {
        isCancelled = true
        job?.cancel()
        job = null
    }

    /**
     * 发送进度
     */
    fun publishProgress(progress: PROGRESS) {
        onTaskProgress(progress)
        progressAction?.invoke(progress)
    }

    /**
     * 事件：任务执行前
     */
    fun before(action: () -> Unit): ProgressTask<PROGRESS, RESULT> {
        beforeAction = action
        return this
    }

    /**
     * 事件：进度
     */
    fun progress(action: (PROGRESS) -> Unit): ProgressTask<PROGRESS, RESULT> {
        progressAction = action
        return this
    }

    /**
     * 事件：任务成功
     */
    fun success(action: (RESULT) -> Unit): ProgressTask<PROGRESS, RESULT> {
        successAction = action
        return this
    }

    /**
     * 事件：任务出错
     */
    fun error(action: (Throwable) -> Unit): ProgressTask<PROGRESS, RESULT> {
        errorAction = action
        return this
    }

    /**
     * 事件：任务主动中断
     */
    fun interrupted(action: () -> Unit): ProgressTask<PROGRESS, RESULT> {
        interruptedAction = action
        return this
    }

    open fun onTaskBefore() {}

    @Throws(Exception::class)
    protected open suspend fun onTaskRun(): RESULT {
        throw NotImplementedError()
    }

    open fun onTaskProgress(progress: PROGRESS) {}

    open fun onTaskSuccess(result: RESULT) {}

    open fun onTaskError(error: Throwable) {}

    open fun onTaskInterrupted() {}

    private var proxy = object : ITaskProxy {
        override fun start() {
            this@ProgressTask.start()
        }

        override fun stop() {
            this@ProgressTask.cancel()
        }
    }

    override fun getProxy(): ITaskProxy {
        return proxy
    }
}